package game;

import java.awt.*;
import java.util.*;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import xml.XMLcommon;
import xml.XMLizeable;

/**
 * Representation of a chess board. It has a <tt>Set</tt> of <tt>Piece</tt> which are on it. 
 * It can add/remove <tt>Piece</tt> to/from itself. 
 * It can identify if it has a <tt>Piece</tt> at a given location and return the on a square, given its location.
 * 
 * @author Mark Kelly
 * @date February 20, 2010
 * @version 2.0
 * 
 * @author Trevor Bastian
 * @date March 28, 2010
 * @version 4.0
 */
public class ChessBoard implements MoveChecker, XMLizeable {
	// Class Variables
	private static final Dimension DEFAULT_SIZE = new Dimension(8, 8);
	
	//Instance Variables
	private HashMap<Point,Piece> pieces;
	private Dimension size;
	private int numSquares;
	
	/**
	 * Creates a new instance of ChessBoard.
	 * If the set of pieces is too large to fit on the ChessBoard, none of them are added.
	 * 
	 * @param size the size of this ChessBoard
	 * @param pieces a set of pieces that should be placed on the ChessBoard.
	 */
	public ChessBoard(Dimension size, Collection<Piece> pieces) {
		this.size = size;
		this.numSquares = this.size.height * this.size.width;
		this.pieces = new HashMap<Point,Piece>();
		
		if (getNumSquares() >= pieces.size()) {
			// TODO throw exceptions
			addAllPieces(pieces);
		}
	}

	/**
	 * Creates a new instance of ChessBoard.
	 * If the set of pieces is too large to fit on the ChessBoard, none of them are added.
	 * 
	 * @param height the height of this ChessBoard
	 * @param width the width of this CHessBoard
	 * @param pieces a set of pieces that should be placed on the ChessBoard.
	 */
	public ChessBoard(int height, int width, Collection<Piece> pieces) {
		this(new Dimension(height, width), pieces);
	}
	
	/**
	 * Creates a new instance of ChessBoard with a default size, populated by the given pieces.
	 * 
	 * @param pieces a set of chess pieces to be added to the board
	 */
	public ChessBoard(Collection<Piece> pieces) {
		this(ChessBoard.DEFAULT_SIZE, pieces);
	}
	
	/**
	 * Create a new instance of <tt>ChessBoard</tt> with the specified size and no <tt>Piece</tt> on it.
	 * 
	 * @param size the size of this<tt>ChessBoard</tt>
	 */
	public ChessBoard(Dimension size) {
		this(size, new HashSet<Piece>());
	}
	
	/**
	 * Creates a new instance of ChessBoard with a default size and no pieces.
	 */
	public ChessBoard() {
		this(new HashSet<Piece>());
	}
	
	/**
	 * Checks whether a given point is on this <tt>ChessBoard</tt>.
	 * 
	 * @param point the point to check
	 * 
	 * @return <tt>true</tt> if the location is on this board
	 */
	private boolean isValidLocation(Point point) {
		if ((point.x > size.width)
				|| (point.y > size.height)
				|| (point.x < 1)
				|| (point.y < 1)) {
			return false;
		}
		else {
			return true;
		}
	}
	
	/**
	 * Checks whether a given point is a valid location for a move, in the context of this <tt>ChessBoard</tt>.
	 * 
	 * @param dest the location to check
	 * @return <tt>true</tt> if the location is valid
	 */
	public boolean isValidMove(Piece piece, Point dest) {
		return isValidLocation(dest);
	}
	
	/**
	 * Returns whether or not this ChessBoard has a Piece at a given location.
	 * 
	 * @param point the location at which to check for a <tt>Piece</tt>
	 * 
	 * @return <tt>true</tt> if there is a <tt>Piece</tt> at the location
	 */
	public boolean hasPieceAt(Point point) {
		// If the location is not on this board, there is no piece there
		if (!isValidLocation(point))
			return false;
		
		return pieces.containsKey(point);
	}
	
	/**
	 * Returns a reference to the piece at the given location.
	 * 
	 * @param point the location to check
	 * 
	 * @return a reference to the <tt>Piece</tt> at the location, or <tt>null</tt> if there is nothing
	 */
	public Piece getPieceAt(Point point) {
		// if the location is not on the board, there is nothing there
		if (!isValidLocation(point))
			return null;
		
		return pieces.get(point);
	}
	
	/**
	 * Returns a reference to the piece at the given location.
	 * 
	 * @param x the x coordinate of the location
	 * @param y the y coordinate of the location
	 * 
	 * @return a reference to the <tt>Piece</tt> at the location, or <tt>null</tt> if there is nothing
	 */
	public Piece getPieceAt(int x, int y) {
		return getPieceAt(new Point(x, y));
	}
	
	/**
	 * Add a <tt>Piece</tt> to this <tt>ChessBoard</tt> at the given location.
	 * If the position of the <tt>Piece</tt> is not within the bounds of this <tt>ChessBoard</tt>, it will not be added.
	 * If the <tt>Piece</tt> is already on this <tt>ChessBoard</tt>, it will not be added again. 
	 * If there is already a <tt>Piece</tt> at the specified location, the new <tt>Piece</tt> will not be added.
	 * If this <tt>ChessBoard</tt> is full, the <tt>Piece</tt> will not be added.
	 * 
	 * @param piece the <tt>Piece</tt> to be added to this <tt>ChessBoard</tt>
	 */
	public void addPiece(Piece piece) {
		Point dest = piece.getLocation();
		
		// Is the board already full?
		if (pieces.size() == getNumSquares()) { // yes
			return;
		}
		// Will the piece be within the bounds of the board?
		else if (!isValidLocation(dest)) { // no
			return;
		}
		// Is there already a Piece at the location?
		else if (hasPieceAt(dest)) { // yes
			return;
		}
		
		// add the piece
		pieces.put(piece.getLocation(), piece);
	}
	
	/**
	 * Remove the specified Piece from the board.
	 * 
	 * @param piece the piece to be removed
	 */
	public void removePiece(Piece piece) {
		pieces.remove(piece.getLocation());
	}
	
	/**
	 * Removes a <tt>Piece</tt> from the specified location.
	 * 
	 * @param point the location to remove a <tt>Piece</tt> from
	 * 
	 * @return a reference to the removed <tt>Piece</tt> if there was one, or <tt>null</tt> if there was not
	 */
	public Piece removePieceAt(Point point) {
		// Is point on this board?
		if (!isValidLocation(point)) {
			return null;
		}
		
		// save a reference to the Piece
		Piece temp = getPieceAt(point);
		// remove the Piece
		removePiece(temp);
		// return a reference to the Piece
		return temp;
	}
	
	/**
	 * Returns the number of squares on this <tt>ChessBoard</TT>
	 * 
	 * @return the number of squares on this <tt>ChessBoard</tt>
	 */
	public int getNumSquares() {
		return numSquares;
	}
	
	/**
	 * Returns the size of this <tt>ChessBoard</tt>
	 * 
	 * @return the size of this <tt>ChessBoard</tt>
	 */
	public Dimension getSize() {
		return new Dimension(size);
	}

	/**
	 * Returns a <tt>Set</tt> containing every <tt>Piece</tt> on this <tt>ChessBoard</tt>
	 * 
	 * @return a <tt>Set</tt> containing every <tt>Piece</tt> on this <tt>ChessBoard</tt>
	 */
	public Collection<Piece> getAllPieces() {
		return pieces.values();
	}
	
	/**
	 * Adds every <tt>Piece</tt> in a <tt>Colection</tt>
	 * 
	 * @param pieces the {@code Collection<Piece>} to add
	 */
	public void addAllPieces(Collection<Piece> pieces) {
		// TODO make this not O(n^2)
		for (Piece p : pieces)
			addPiece(p);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString()
	{
		Piece p = null;
		Point point = null;
		
		StringBuilder board = new StringBuilder("");
		board.append("\n  _________________________________________\n");
		for ( int i = 1; i <= 8; i ++ ) {
			board.append((9 - i) + " |");
			for ( int j = 1; j <= 8; j++ ) {
				point = new Point(j, 9 - i);
				if (hasPieceAt(point)) {
					p = getPieceAt(point);
					board.append(" " + p.getName() + " |");
				} else {
					board.append("    |");
				}
			}
			board.append("\n  _________________________________________\n");
		}
		board.append("\n     A    B    C    D    E    F    G    H  \n");
		return board.toString();
	}
	
	/**
	 * Moves the {@code Piece} to the destination. Assumes that all move checking has already been done!
	 * 
	 * @param piece the {@code Piece} to move
	 * @param dest the destination to move it to
	 */
	public void movePiece(Piece piece, Point dest)
	{
		if (hasPieceAt(piece.getLocation()))
			this.removePiece(piece);
		
		piece.setLocation(dest);
		this.addPiece(piece);
	}
	
	/**
	 * <p>Returns a Set of {@code Pieces} that are of type {@code T} and are on {@code Team} team.</p>
	 * 
	 * @param <T> the type of {@code Pieces} that are wanted
	 * @param clazz a {@code Class} representing {@code T}
	 * @param team the {@code Team} that the {@code Pieces}
	 * @return a {@code Set} of {@code Pieces}
	 */
	public <T extends Piece> java.util.List<Piece> getPieceList(Class<T> clazz, Team team) {
		LinkedList<Piece> set = new LinkedList<Piece>();
		
		// Check every Piece on this ChessBoard
		for (Piece p : pieces.values()) {
			Class<?>pClass = p.getClass();
			
			if (p.getTeam().equals(team)) // check Team first
				if (pClass.equals(clazz) || pClass.getSuperclass().equals(clazz)) // check class
					set.add(p);
		}
		
		return set;
	}

	/**
	 * Creates a chess board from elem
	 * @param elem
	 */
	private ChessBoard (Element elem) {
		this.pieces = new HashMap<Point,Piece>();
		String className = Dimension.class.getName();
		size = XMLcommon.parseXMLDimension((Element)elem.getElementsByTagName(className).item(0));
		numSquares = size.height * size.width;

		NodeList nl = elem.getElementsByTagName(Piece.class.getName());
		for (int i = 0; i < nl.getLength(); i++) {
			this.addPiece (Piece.importFromXML((Element)nl.item(i)));
		}
	}
	
	/**
	 * Returns a ChessBoard represented by elem
	 * @param elem
	 * @return
	 */
	public static ChessBoard importFromXML(Element elem) {
		return new ChessBoard (elem);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Element toXML(Document doc) {
		Element board = doc.createElement(ChessBoard.class.getName());
		board.appendChild(XMLcommon.dimensionToXML(doc, size));
		
		for (Piece p: getAllPieces()) {
			board.appendChild(p.toXML(doc));
		}
		
		return board;
	}
}
